Системное программирование

Тема 6. Организация графического пользовательского интерфейса в операционных системах

Системное программирование

План лекции

1. Ресурсы приложения, их создание и использование

2. Организация интерфейса на основе меню

3. Использование диалогов

4. Принципы построения графической подсистемы ОС Windows

5. Понятие контекста устройства

6. Графические инструменты

7. Прикладной интерфейс для обработки пользовательского ввода

8. Организация графического пользовательского интерфейса, графические подсистемы, графический вывод

9. Графический вывод в Linux (Cairo, GTK, Qt)

10. Кроссплатформенные решения (SDL, абстракция платформы)

11. Низкоуровневые графические API (DirectX, Vulkan, Metal)

Организация графического пользовательского интерфейса
Системное программирование

1. Ресурсы приложения

1.1. Типы ресурсов

Ресурсы — данные, встроенные в исполняемый файл.

Тип ресурса Расширение Назначение
Иконки .ico Значки приложения
Курсоры .cur Указатели мыши
Битмапы .bmp Изображения
Строки Текстовые данные
Диалоги Шаблоны окон
Меню Структуры меню
Версия Информация о файле
Организация графического пользовательского интерфейса
Системное программирование

1.2. Файл ресурсов (.rc)

// Иконка
IDI_APPICON ICON "app.ico"

// Курсор
IDC_MYCURSOR CURSOR "cursor.cur"

// Битмап
IDB_BACKGROUND BITMAP "bg.bmp"

// Строковая таблица
STRINGTABLE
BEGIN
    IDS_APP_TITLE       "Мое приложение"
    IDS_ERROR_MESSAGE   "Произошла ошибка"
END

// Версия
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        VALUE "FileDescription", "Мое приложение"
        VALUE "FileVersion", "1.0.0.0"
    END
END
Организация графического пользовательского интерфейса
Системное программирование

1.3. Использование ресурсов

// Загрузка иконки
HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPICON));

// Загрузка курсора
HCURSOR hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYCURSOR));

// Загрузка битмапа
HBITMAP hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BACKGROUND));

// Загрузка строки
WCHAR buffer[256];
LoadString(hInstance, IDS_APP_TITLE, buffer, 256);
Организация графического пользовательского интерфейса
Системное программирование

2. Организация меню

2.1. Типы меню

Виды меню:

  • Главное меню — строка в верхней части окна
  • Контекстное меню — появляется по правому клику
  • Системное меню — кнопка в заголовке окна

Организация графического пользовательского интерфейса
Системное программирование

2.2. Создание меню

В файле ресурсов:

IDR_MAINMENU MENU
BEGIN
    POPUP "&Файл"
    BEGIN
        MENUITEM "&Создать\tCtrl+N", ID_FILE_NEW
        MENUITEM "&Открыть\tCtrl+O", ID_FILE_OPEN
        MENUITEM SEPARATOR
        MENUITEM "&Сохранить\tCtrl+S", ID_FILE_SAVE
        MENUITEM SEPARATOR
        MENUITEM "В&ыход", ID_FILE_EXIT
    END
    
    POPUP "&Правка"
    BEGIN
        MENUITEM "&Отменить\tCtrl+Z", ID_EDIT_UNDO
        MENUITEM "&Вырезать\tCtrl+X", ID_EDIT_CUT
        MENUITEM "&Копировать\tCtrl+C", ID_EDIT_COPY
        MENUITEM "В&ставить\tCtrl+V", ID_EDIT_PASTE
    END
END
Организация графического пользовательского интерфейса
Системное программирование

2.3. Программное создание меню

// Создание меню
HMENU hMenu = CreateMenu();
HMENU hFileMenu = CreatePopupMenu();

// Добавление пунктов
AppendMenu(hFileMenu, MF_STRING, ID_FILE_NEW, L"&Создать");
AppendMenu(hFileMenu, MF_STRING, ID_FILE_OPEN, L"&Открыть");
AppendMenu(hFileMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hFileMenu, MF_STRING, ID_FILE_EXIT, L"В&ыход");

// Добавление подменю
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hFileMenu, L"&Файл");

// Установка меню окну
SetMenu(hWnd, hMenu);
Организация графического пользовательского интерфейса
Системное программирование

2.4. Обработка команд меню

case WM_COMMAND:
    switch (LOWORD(wParam)) {
        case ID_FILE_NEW:
            // Создать новый файл
            break;
        case ID_FILE_OPEN:
            // Открыть файл
            break;
        case ID_FILE_SAVE:
            // Сохранить файл
            break;
        case ID_FILE_EXIT:
            PostQuitMessage(0);
            break;
    }
    break;
Организация графического пользовательского интерфейса
Системное программирование

3. Диалоговые окна

3.1. Типы диалогов

Модальные диалоги:

  • Блокируют родительское окно
  • Требуют немедленного ответа
  • Пример: MessageBox, Save Dialog

Немодальные диалоги:

  • Не блокируют родительское окно
  • Работают параллельно
  • Пример: Find Dialog, Tool Window
Организация графического пользовательского интерфейса
Системное программирование

3.2. Шаблон диалога

IDD_ABOUTBOX DIALOGEX 0, 0, 200, 100
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "О программе"
FONT 8, "MS Shell Dlg"
BEGIN
    ICON            IDI_APPICON, IDC_STATIC, 10, 10, 20, 20
    LTEXT           "Мое приложение v1.0", IDC_STATIC, 40, 10, 150, 10
    LTEXT           "(c) 2025 Компания", IDC_STATIC, 40, 25, 150, 10
    DEFPUSHBUTTON   "OK", IDOK, 70, 70, 60, 15
END
Организация графического пользовательского интерфейса
Системное программирование

3.3. Создание диалога

// Модальный диалог
INT_PTR result = DialogBox(
    hInstance,          // Экземпляр
    MAKEINTRESOURCE(IDD_ABOUTBOX),  // Шаблон
    hWndParent,         // Родитель
    AboutDlgProc        // Процедура диалога
);

// Немодальный диалог
HWND hDlg = CreateDialog(
    hInstance,
    MAKEINTRESOURCE(IDD_FIND),
    hWndParent,
    FindDlgProc
);

// Показать
ShowWindow(hDlg, SW_SHOW);
Организация графического пользовательского интерфейса
Системное программирование

3.4. Процедура диалога

INT_PTR CALLBACK AboutDlgProc(
    HWND hDlg,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
) {
    switch (message) {
        case WM_INITDIALOG:
            // Инициализация
            return TRUE;
            
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case IDOK:
                case IDCANCEL:
                    EndDialog(hDlg, LOWORD(wParam));
                    return TRUE;
            }
            break;
    }
    return FALSE;  // Не обработано
}
Организация графического пользовательского интерфейса
Системное программирование

4. Графическая подсистема Windows

4.1. Архитектура GDI

GDI (Graphics Device Interface) — интерфейс графических устройств.

Организация графического пользовательского интерфейса
Системное программирование

4.2. Компоненты GDI

Основные элементы:

  • Контекст устройства (DC) — поверхность рисования
  • Перо (Pen) — линии и границы
  • Кисть (Brush) — заливка
  • Шрифт (Font) — текст
  • Регион (Region) — области
  • Битмап (Bitmap) — изображения
Организация графического пользовательского интерфейса
Системное программирование

5. Контекст устройства

5.1. Понятие DC

Device Context — структура, описывающая поверхность рисования и инструменты.

Типы DC:

  • Экранный — окно или весь экран
  • Принтерный — страница принтера
  • Памяти — битмап в памяти
// Получение DC окна
HDC hdc = GetDC(hWnd);

// Рисование...

// Освобождение
ReleaseDC(hWnd, hdc);
Организация графического пользовательского интерфейса
Системное программирование

5.2. Обработка WM_PAINT

case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    
    // Рисование в hdc
    
    EndPaint(hWnd, &ps);
    return 0;
}

PAINTSTRUCT содержит:

  • hdc — контекст устройства
  • rcPaint — прямоугольник для перерисовки
  • fErase — нужно ли стирать фон
Организация графического пользовательского интерфейса
Системное программирование

6. Графические инструменты

6.1. Перо (Pen)

// Создание пера
HPEN hPen = CreatePen(
    PS_SOLID,      // Стиль: сплошная линия
    3,             // Ширина в пикселях
    RGB(255, 0, 0) // Цвет: красный
);

// Выбор в DC
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);

// Рисование
MoveToEx(hdc, 10, 10, NULL);
LineTo(hdc, 100, 100);

// Восстановление и удаление
SelectObject(hdc, hOldPen);
DeleteObject(hPen);

Стили пера:

  • PS_SOLID — сплошная
  • PS_DASH — штриховая
  • PS_DOT — пунктирная
  • PS_DASHDOT — штрих-пунктир
Организация графического пользовательского интерфейса
Системное программирование

6.2. Кисть (Brush)

// Сплошная кисть
HBRUSH hBrush = CreateSolidBrush(RGB(0, 128, 255));

// Штриховая кисть
HBRUSH hHatchBrush = CreateHatchBrush(
    HS_DIAGCROSS,      // Стиль штриховки
    RGB(0, 0, 0)
);

// Системная кисть
HBRUSH hSysBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);

// Использование
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
Rectangle(hdc, 10, 10, 100, 100);
SelectObject(hdc, hOldBrush);
DeleteObject(hBrush);
Организация графического пользовательского интерфейса
Системное программирование

6.3. Рисование фигур

// Прямоугольник
Rectangle(hdc, left, top, right, bottom);

// Эллипс
Ellipse(hdc, left, top, right, bottom);

// Скругленный прямоугольник
RoundRect(hdc, left, top, right, bottom, width, height);

// Многоугольник
POINT points[] = {{10, 10}, {50, 100}, {100, 50}};
Polygon(hdc, points, 3);

// Дуга
Arc(hdc, left, top, right, bottom, xStart, yStart, xEnd, yEnd);
Организация графического пользовательского интерфейса
Системное программирование

6.4. Текст и шрифты

// Создание шрифта
HFONT hFont = CreateFont(
    24,              // Высота
    0,               // Ширина (0 = авто)
    0,               // Угол наклона
    0,               // Ориентация
    FW_BOLD,         // Жирность
    FALSE,           // Курсив
    FALSE,           // Подчеркивание
    FALSE,           // Зачеркивание
    DEFAULT_CHARSET, // Кодировка
    OUT_DEFAULT_PRECIS,
    CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY,
    DEFAULT_PITCH | FF_SWISS,
    L"Arial"
);

// Установка и вывод
SelectObject(hdc, hFont);
TextOut(hdc, 10, 10, L"Привет, мир!", 12);
Организация графического пользовательского интерфейса
Системное программирование

7. Обработка пользовательского ввода

7.1. Клавиатурный ввод

case WM_KEYDOWN:
    switch (wParam) {
        case VK_LEFT:
            // Стрелка влево
            break;
        case VK_RIGHT:
            // Стрелка вправо
            break;
        case VK_SPACE:
            // Пробел
            break;
    }
    break;

case WM_CHAR:
    // Символ с учетом раскладки
    wchar_t ch = (wchar_t)wParam;
    break;
Организация графического пользовательского интерфейса
Системное программирование

7.2. Мышь

case WM_LBUTTONDOWN:
    // Левая кнопка нажата
    int x = GET_X_LPARAM(lParam);
    int y = GET_Y_LPARAM(lParam);
    break;

case WM_RBUTTONDOWN:
    // Правая кнопка
    break;

case WM_MOUSEMOVE:
    // Движение мыши
    break;

case WM_LBUTTONDBLCLK:
    // Двойной клик
    break;
Организация графического пользовательского интерфейса
Системное программирование

8. Графический вывод

8.1. Двойная буферизация

Проблема мерцания:

// Создание совместимого DC
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmMem = CreateCompatibleBitmap(hdc, width, height);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);

// Рисование в памяти
FillRect(hdcMem, &rect, hBrush);
// ... другие операции

// Копирование на экран
BitBlt(hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);

// Очистка
SelectObject(hdcMem, hbmOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
Организация графического пользовательского интерфейса
Системное программирование

8.2. Работа с битмапами

// Загрузка из файла
HBITMAP hBitmap = (HBITMAP)LoadImage(
    NULL,
    L"image.bmp",
    IMAGE_BITMAP,
    0, 0,
    LR_LOADFROMFILE
);

// Создание совместимого DC
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hOld = (HBITMAP)SelectObject(hdcMem, hBitmap);

// Вывод
BitBlt(hdc, x, y, width, height, hdcMem, 0, 0, SRCCOPY);

// Растяжение
StretchBlt(hdc, x, y, newWidth, newHeight,
           hdcMem, 0, 0, srcWidth, srcHeight, SRCCOPY);

// Очистка
    SelectObject(hdcMem, hOld);
    DeleteDC(hdcMem);
    DeleteObject(hBitmap);
Организация графического пользовательского интерфейса
Системное программирование

9. Графический вывод в Linux

9.1. Графические библиотеки Linux

Cairo — векторная графическая библиотека:

#include <cairo.h>
#include <cairo-xlib.h>
#include <X11/Xlib.h>

int main() {
    Display* dpy = XOpenDisplay(NULL);
    int screen = DefaultScreen(dpy);
    Window win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen),
                                      10, 10, 400, 300, 0,
                                      BlackPixel(dpy, screen),
                                      WhitePixel(dpy, screen));
    XMapWindow(dpy, win);
    
    // Создание Cairo surface
    cairo_surface_t* surface = cairo_xlib_surface_create(
        dpy, win, DefaultVisual(dpy, screen), 400, 300);
    cairo_t* cr = cairo_create(surface);
    // Рисование
    cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);  // Красный
    cairo_rectangle(cr, 50, 50, 100, 80);
    cairo_fill(cr);
    
    // Текст
    cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
    cairo_move_to(cr, 50, 200);
    cairo_show_text(cr, "Hello, Cairo!");
    
    cairo_destroy(cr);
    cairo_surface_destroy(surface);
    XCloseDisplay(dpy);
    return 0;
}
Организация графического пользовательского интерфейса
Системное программирование

Компиляция:

gcc -o cairo_demo main.c `pkg-config --cflags --libs cairo x11`
Организация графического пользовательского интерфейса
Системное программирование

9.2. GTK Drawing Area

#include <gtk/gtk.h>

static void draw_callback(GtkDrawingArea* area, cairo_t* cr,
                          int width, int height, gpointer data) {
    // Фон
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    cairo_paint(cr);
    
    // Красный прямоугольник
    cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
    cairo_rectangle(cr, 50, 50, 100, 80);
    cairo_fill(cr);
    
    // Синий круг
    cairo_set_source_rgb(cr, 0.0, 0.0, 1.0);
    cairo_arc(cr, 250, 150, 50, 0, 2 * M_PI);
    cairo_fill(cr);
    
    // Линия
    cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
    cairo_set_line_width(cr, 3.0);
    cairo_move_to(cr, 0, 0);
    cairo_line_to(cr, width, height);
    cairo_stroke(cr);
}


static void on_activate(GtkApplication* app) {
    GtkWindow* window = GTK_WINDOW(gtk_application_window_new(app));
    gtk_window_set_title(window, "GTK Drawing");
    gtk_window_set_default_size(window, 400, 300);
    
    GtkDrawingArea* area = GTK_DRAWING_AREA(gtk_drawing_area_new());
    gtk_drawing_area_set_draw_func(area, draw_callback, NULL, NULL);
    gtk_window_set_child(window, GTK_WIDGET(area));
    
    gtk_window_present(window);
}

int main(int argc, char** argv) {
    GtkApplication* app = gtk_application_new("org.example.Draw",
                                               G_APPLICATION_DEFAULT_FLAGS);
    g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
    return g_application_run(G_APPLICATION(app), argc, argv);
}
Организация графического пользовательского интерфейса
Системное программирование

9.3. QPainter (Qt)

#include <QApplication>
#include <QWidget>
#include <QPainter>

class DrawingWidget : public QWidget {
protected:
    void paintEvent(QPaintEvent*) override {
        QPainter painter(this);
        
        // Фон
        painter.fillRect(rect(), Qt::white);
        
        // Красный прямоугольник
        painter.setBrush(Qt::red);
        painter.drawRect(50, 50, 100, 80);
        
        // Синий эллипс
        painter.setBrush(Qt::blue);
        painter.drawEllipse(200, 100, 100, 100);
        // Текст
        painter.setPen(Qt::black);
        painter.setFont(QFont("Arial", 14));
        painter.drawText(50, 200, "Hello, Qt!");
        
        // Линия
        painter.setPen(QPen(Qt::green, 3));
        painter.drawLine(0, 0, width(), height());
    }
};

int main(int argc, char** argv) {
    QApplication app(argc, argv);
    DrawingWidget widget;
    widget.setWindowTitle("Qt Drawing");
    widget.resize(400, 300);
    widget.show();
    return app.exec();
}
Организация графического пользовательского интерфейса
Системное программирование

10. Кроссплатформенные решения

10.1. Абстракция платформы

Организация графического пользовательского интерфейса
Системное программирование

10.2. SDL (Simple DirectMedia Layer)

SDL — кроссплатформенная библиотека для мультимедиа.

#include <SDL2/SDL.h>
#include <SDL2/SDL_render.h>

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_VIDEO);
    
    SDL_Window* window = SDL_CreateWindow(
        "SDL Demo",
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        800, 600,
        SDL_WINDOW_SHOWN
    );
    
    SDL_Renderer* renderer = SDL_CreateRenderer(
        window, -1, SDL_RENDERER_ACCELERATED
    );
    
    SDL_bool running = SDL_TRUE;
    while (running) {
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT)
                running = SDL_FALSE;
        }
        // Очистка
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        SDL_RenderClear(renderer);
        
        // Красный прямоугольник
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        SDL_Rect rect = {100, 100, 200, 150};
        SDL_RenderFillRect(renderer, &rect);
        
        SDL_RenderPresent(renderer);
    }
    
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}
Организация графического пользовательского интерфейса
Системное программирование

10.3. Сравнение подходов к GUI

Подход Платформы Сложность Гибкость Производительность
WinAPI Windows Высокая Максимальная Высокая
GTK Linux/Windows/macOS Средняя Высокая Высокая
Qt Все Средняя Высокая Высокая
SDL Все Низкая Средняя Очень высокая
Electron Все Низкая Низкая Низкая
Flutter Все Средняя Высокая Высокая
Организация графического пользовательского интерфейса
Системное программирование

10.4. Выбор технологии

Рекомендации:

Сценарий Рекомендация
Нативное Windows-приложение WinAPI, WPF
Кроссплатформенный десктоп Qt, GTK
Игра, мультимедиа SDL, GLFW
Быстрый прототип Qt, Electron
Минимальные зависимости WinAPI, Xlib
Современный UI Qt Quick, Flutter
Организация графического пользовательского интерфейса
Системное программирование

11. Низкоуровневые графические API

11.1. DirectX

DirectX — набор API от Microsoft для работы с мультимедиа и графикой, доступен только на Windows и Xbox.

История:

Версия Год Ключевые изменения
DirectX 1–7 1995–2000 Начало, DirectDraw, базовый 3D
DirectX 8 2000 Программируемые шейдеры
DirectX 9 2002 Shader Model 2.0/3.0, массовое adoption
DirectX 10 2006 Vista, новая драйверная модель (WDDM)
DirectX 11 2009 Tessellation, compute shaders
DirectX 12 2015 Явное управление GPU, низкоуровневый доступ
Организация графического пользовательского интерфейса
Системное программирование

Основные компоненты:

  • Direct3D — 3D-графика (основной компонент)
  • Direct2D — аппаратно-ускоренная 2D-графика
  • DirectWrite — высококачественный рендеринг текста
  • XAudio2 — аудио
  • DirectInput / XInput — ввод (геймпады)

DirectX 12 предоставляет явный контроль над GPU:

  • Явное управление памятью GPU
  • Командные очереди (command queues) и списки (command lists)
  • Слабая абстракция над аппаратным обеспечением
  • Меньше накладных расходов драйвера
  • Подход «close-to-metal» — программист отвечает за синхронизацию, управление памятью и планирование

Ограничения: Windows 10+ и Xbox только.

Организация графического пользовательского интерфейса
Системное программирование

11.2. Vulkan

Vulkan — кроссплатформенный низкоуровневый API для графики и вычислений, разработан Khronos Group (создателями OpenGL).

Ключевые особенности:

  • Кроссплатформенный: Windows, Linux, Android
  • Явный контроль GPU (аналог DirectX 12)
  • Низкие накладные расходы (low overhead)
  • Многопоточная генерация команд
  • SPIR-V — бинарный формат шейдеров (предварительная компиляция)
  • Единый API для графики и вычислений (compute)
Организация графического пользовательского интерфейса
Системное программирование

Архитектура Vulkan:

SPIR-V — стандартный промежуточный формат шейтеров:

  • Бинарное представление, не текстовое (как GLSL/HLSL)
  • Предварительная компиляция оффлайн
  • Поддерживается Vulkan, OpenGL 4.6, OpenCL
  • Инструменты: glslangValidator, SPIRV-Cross

MoltenVK — реализация Vulkan поверх Metal для macOS/iOS, позволяющая запускать Vulkan-приложения на платформах Apple.

Организация графического пользовательского интерфейса
Системное программирование

11.3. Metal

Metal — графический API от Apple для macOS, iOS, tvOS.

Особенности:

  • Нативный API для платформ Apple
  • Явный контроль над GPU (аналог Vulkan/DirectX 12)
  • Интеграция с Swift и Objective-C
  • Metal Performance Shaders — библиотека оптимизированных шейдеров
  • Metal Compute — GPGPU вычисления
  • Недоступен за пределами экосистемы Apple
Организация графического пользовательского интерфейса
Системное программирование

11.4. Сравнение графических API

Характеристика GDI DirectX 12 Vulkan OpenGL Metal
Платформы Windows Windows, Xbox Windows, Linux, Android Все macOS, iOS
Уровень Высокоуровневый Низкий Низкий Средний Низкий
Производительность Низкая Очень высокая Очень высокая Средняя Очень высокая
Сложность Низкая Высокая Очень высокая Средняя Высокая
Управление GPU Автоматическое Явное Явное Автоматическое Явное
Основное применение UI десктопных приложений Игры, AAA Игры, движки CAD, научные приложения Игры, приложения Apple
Многопоточность Нет Да Да Ограниченная Да
Шейдеры HLSL SPIR-V/GLSL GLSL MSL
Организация графического пользовательского интерфейса
Системное программирование

11.5. Когда что использовать

Сценарий Рекомендуемый API Причина
Простое десктопное приложение (UI) GDI, Cairo, Qt Простота, достаточно производительности
Игры на Windows DirectX 11/12 Лучший инструментарий, HLSL, отладчики
Кроссплатформенные игры/движки Vulkan, OpenGL Переносимость между платформами
Приложения для macOS/iOS Metal Нативная производительность
CAD, визуализация данных OpenGL, Vulkan Кроссплатформенность, стабильность
Встраиваемые системы OpenGL ES, Vulkan Минимальные требования к железу
Профессиональный 3D-рендеринг Vulkan, DirectX 12 Максимальный контроль над GPU
Организация графического пользовательского интерфейса
Системное программирование

Резюме

Ключевые моменты лекции:

  1. Ресурсы — встроенные данные приложения
  2. Меню — организация команд
  3. Диалоги — модальные и немодальные окна
  4. GDI — графическая подсистема Windows
  5. DC — контекст устройства для рисования
  6. Инструменты — перья, кисти, шрифты
  7. Ввод — обработка клавиатуры и мыши
  8. Буферизация — устранение мерцания
  9. Cairo — векторная графика в Linux
  10. Кроссплатформенность — Qt, GTK, SDL
  11. Низкоуровневые API — DirectX, Vulkan, Metal
Организация графического пользовательского интерфейса
Системное программирование

Вопросы для самопроверки

  1. Какие типы ресурсов существуют?
  2. Чем отличается модальный диалог от немодального?
  3. Что такое контекст устройства?
  4. Какие инструменты GDI используются для рисования?
  5. Как работает двойная буферизация?
  6. Как обрабатывать ввод с мыши?
  7. Что такое Cairo и как его использовать?
  8. Какие кроссплатформенные GUI-решения существуют?
Организация графического пользовательского интерфейса
Системное программирование

Практические задания

Самостоятельная работа (8 часов):

  1. Создать приложение с меню и диалогом в WinAPI
  2. Реализовать рисование с помощью GDI
  3. Написать Cairo-приложение для Linux
  4. Создать простое SDL-приложение
Организация графического пользовательского интерфейса
Системное программирование

Рекомендуемая литература

Основная:

  1. Таненбаум, Э. Современные операционные системы. — 4-е изд. — СПб.: Питер, 2021.
  2. Kerrisk, M. The Linux Programming Interface. — No Starch Press, 2010.

Дополнительная:

  1. MSDN: GDI, Device Contexts, Resources
  2. Cairo Documentation: https://www.cairographics.org/documentation/
  3. SDL Documentation: https://wiki.libsdl.org/
  4. Qt Documentation: https://doc.qt.io/
  5. GTK Documentation: https://docs.gtk.org/
Организация графического пользовательского интерфейса

Кратко пробежаться по плану, подчеркнуть, что лекция делится на две большие части — Windows (WinAPI/GDI) и Linux (Cairo/GTK/Qt), и завершается кроссплатформенными решениями. Спросить, кто уже работал с WinAPI или Cairo.

Подчеркнуть, что ресурсы компилируются в .exe/.dll и не требуют отдельных файлов при распространении. Обратить внимание на строковые таблицы — это основа локализации (i18n). Спросить, зачем хранить строки в ресурсах, а не прямо в коде.

Показать, что rc-файл обрабатывается компилятором ресурсов (rc.exe) и генерирует .res, который линкуется в exe. Упомянуть, что для Linux-приложений аналогом служат embedded resources (Qt .qrc, GLib GResource).

Объяснить MAKEINTRESOURCE — это макрос, преобразующий числовой ID в LPCTSTR. Напомнить, что каждый загруженный GDI-объект нужно освобождать (DeleteObject). Типичная ошибка — утечка ресурсов при повторной загрузке.

Обратить внимание на амперсанд (&) — он задаёт мнемоническую клавишу (Alt+буква). Спросить, чем системное меню отличается от пользовательского (можно добавлять свои пункты через AppendMenu).

Показать, что \t в строке меню — это табуляция, которая визуально выравнивает сочетание клавиш вправо. Акцентировать внимание на SEPARATOR и соглашении об именовании ID (префикс по группе: ID_FILE_, ID_EDIT_).

Указать, что программное создание полезно для динамических меню (например, список последних файлов). Отметить, что AppendMenu считается устаревшим — лучше использовать InsertMenuItem. Напомнить про необходимость DestroyMenu при динамическом создании.

Ключевой момент: ВСЕ элементы меню (и кнопки, и акселераторы) генерируют WM_COMMAND. LOWORD(wParam) — идентификатор, HIWORD(wParam) — код уведомления. Спросить, как отличить команду меню от нажатия кнопки (по HIWORD: для меню — 0, для кнопок — BN_CLICKED).

Спросить студентов, приведут ли они примеры модальных/немодальных диалогов из известных программ. Подчеркнуть, что модальный диалог имеет свой цикл сообщений. Обратить внимание на UX: чрезмерное использование модальных диалогов раздражает пользователей.

Объяснить параметры DIALOGEX: x, y, width, height — в диалоговых единицах (DLU), а не пикселях. DEFPUSHBUTTON — кнопка по умолчанию (нажимается Enter). IDC_STATIC — специальный ID для статических элементов (не обрабатывается в коде).

DialogBox — блокирующий вызов, создаёт собственный цикл сообщений. CreateDialog — возвращает HWND немедленно, сообщение WM_INITDIALOG приходит асинхронно. Частая ошибка: забывать вызывать ShowWindow для немодального диалога.

Отличие от WndProc: возвращает BOOL (TRUE/FALSE), а не LRESULT. WM_INITDIALOG вместо WM_CREATE. EndDialog — аналог DestroyWindow для модальных диалогов. Спросить, что вернёт DialogBox, если закрыть через крестик (IDCANCEL).

Подчеркнуть, что GDI обеспечивает аппаратную независимость — приложение рисует одинаково на экране и принтере. Упомянуть эволюцию: GDI → GDI+ → Direct2D. GDI устарел, но всё ещё используется и важен для понимания основ.

Все объекты GDI (кроме DC) имеют тип HGDIOBJ и должны удаляться через DeleteObject. Напомнить паттерн: SelectObject → рисование → SelectObject(old) → DeleteObject. Спросить, к какому типу относится палитра (Palette).

Важное правило: GetDC/ReleaseDC — пара. Полученный DC нужно обязательно освободить. В отличие от GetDC, BeginPaint/EndPaint вызываются ТОЛЬКО в обработчике WM_PAINT. Спросить, что будет, если рисовать вне WM_PAINT (рисунок пропадёт при перерисовке).

WM_PAINT генерируется только когда система определяет, что часть окна нужна в перерисовке. BeginPaint валидирует обновлённую область. Если WM_PAINT не обработан — система будет отправлять его бесконечно. Спросить, как принудительно вызвать перерисовку (InvalidateRect / UpdateWindow).

Подчеркнуть паттерн сохранения/восстановления: SelectObject возвращает старый объект, его нужно сохранить и восстановить перед DeleteObject. Штриховые стили работают только при толщине 1 пиксель — частая ошибка студентов.

Важно: объекты, полученные через GetStockObject, НЕ нужно удалять (DeleteObject для них не вызывается). Это частая ошибка. Кисть определяет заливку замкнутых фигур, перо — обводку.

Обратить внимание: для Rectangle и Ellipse координаты задают ограничивающий прямоугольник, а не центр. Arc использует углы в десятых долях градуса (xStart, yStart — начальная точка, xEnd, yEnd — конечная). Все фигуры используют текущие перо и кисть из DC.

CreateFont имеет 14 параметров — больше, чем у большинства функций WinAPI. Упомянуть более удобную CreateFontIndirect (принимает структуру LOGFONT). Обратить внимание на высоту шрифта: отрицательное значение — высота символа, положительное — высота ячейки.

Ключевое различие: WM_KEYDOWN — виртуальный код клавиши (VK_), не зависит от раскладки. WM_CHAR — символьный код, зависит от языка ввода. WM_KEYDOWN → WM_CHAR — последовательность для печатных символов. Спросить, для чего лучше использовать WM_KEYDOWN (управление, игра), а для чего WM_CHAR (ввод текста).

Подчеркнуть, что координаты мыши в lParam — клиентские (относительно окна), а не экранные. Для экранных координат — ClientToScreen. Чтобы получать WM_LBUTTONDBLCLK, необходимо зарегистрировать класс окна с CS_DBLCLKS. Частая ошибка: использовать LOWORD/HIWORD вместо GET_X_LPARAM/GET_Y_LPARAM (проблемы на multi-monitor).

Это один из важнейших слайдов лекции. Подчеркнуть: рисуем в памяти → одним вызовом BitBlt копируем на экран. Без буферизации — мерцание, т.к. каждая операция рисования сразу видна на экране. SRCCOPY — режим копирования без трансформации. Спросить, какие ещё есть ROP-коды (NOTSRCERASE, SRCAND, SRCINVERT).

LoadImage поддерживает BMP, ICO, CUR. Для других форматов (PNG, JPEG) нужна GDI+ или сторонняя библиотека (stb_image, libpng). StretchBlt позволяет масштабировать, но без антиалиасинга — для качественного масштабирования нужен GDI+.

Это объёмный слайд — не застревать на синтаксисе каждого примера. Главное: Cairo — низкоуровневая библиотека для 2D-графики (используется внутри GTK). GTK — высокоуровневый тулкит. Qt/QPainter — объектно-ориентированный подход (наиболее привычный для C++-разработчиков). Сравнить с WinAPI GDI: Cairo использует состояние (state machine), GDI — объекты в DC.

Ключевой вывод лекции: выбор технологии зависит от задачи. SDL — не GUI-тулkit, а низкоуровневый доступ к окну и вводу (игры, эмуляторы). Для десктопных приложений с виджетами — Qt или GTK. Упомянуть Wayland как замену X11 — это актуально для современного Linux.

Этот раздел — обзор, не углубляемся в синтаксис. Главное: понять, что существует иерархия графических API — от высокоуровневых (GDI, Cairo) до низкоуровневых (Vulkan, DirectX 12, Metal). Низкоуровневые дают больше контроля и производительности, но требуют значительно больше кода. Для сравнения: «Hello Triangle» на GDI — ~30 строк, на Vulkan — ~1000 строк.

Пробежаться по пунктам, попросить студентов назвать, какой раздел показался наиболее сложным. Вернуться к плану лекции и убедиться, что все темы покрыты.

Рекомендовать студентам ответить на вопросы устно в парах. Вопросы 3, 5 и 7 — наиболее проблемные, обсудить их подробно, если останется время.

Задания рассчитаны на лабораторные работы. Задание 1 — самое базовое. Задание 2 — можно совмещать с заданием 1 (добавить Drawing Area). Задания 3 и 4 — для работы в Linux. Напомнить про компиляцию: pkg-config для Cairo/GTK, sdl2-config для SDL.